#include "audio_encoder1.h"

my_encoder_t my_encoder;
esp_audio_enc_handle_t encoder = NULL;

void audio_encoder_init(void)
{
    // 初始化配置
    esp_opus_enc_config_t opus_cfg = {

        .sample_rate = 16000,                                // 采样率
        .channel = 1,                                        // 单通道
        .bits_per_sample = 16,                               // 采样位数
        .bitrate = 32000,                                    // 小智的是要求60ms和16000的采样率
        .frame_duration = ESP_OPUS_ENC_FRAME_DURATION_60_MS, // 帧时长
        .application_mode = ESP_OPUS_ENC_APPLICATION_AUDIO,  // 应用模式: 音频
        .complexity = 5,                                     // 复杂度: 0-10
        .enable_fec = false,
        .enable_dtx = false,
        .enable_vbr = false};
    // 打开编码器
    //&opus_cfg: 指向配置结构体的指针。
    // sizeof(esp_opus_enc_config_t): 传入配置结构体的大小，这是一种常见的做法，确保函数内部处理的是正确大小的内存块，有助于版本兼容性。
    //&enc_hd: 这是一个 esp_opus_enc_handle_t 类型变量的地址。函数成功执行后，会在这个变量 enc_hd 中填入一个编码器句柄。这个句柄代表了刚刚创建的那个编码器实例，在后续的所有操作（如编码、关闭）中，都需要使用这个句柄来指定对哪个编码器进行操作。
    esp_opus_enc_open(&opus_cfg, sizeof(esp_opus_enc_config_t), &encoder);
}

// 实现了一个 Opus 音频编码任务，它是一个在 FreeRTOS 实时操作系统上运行的独立线程，专门负责从输入环形缓冲区读取原始的 PCM 音频数据，进行压缩编码（Opus），然后将编码后的数据送入输出环形缓冲区。
void audio_encoder_task(void*);
void audio_encoder_start(RingbufHandle_t input, RingbufHandle_t output)
{
    my_encoder.input = input;
    my_encoder.output = output;

    my_encoder.is_running = true;

    xTaskCreatePinnedToCoreWithCaps(audio_encoder_task,
                                    "audio_encoder_task",
                                    64 * 1024,
                                    NULL,
                                    5,
                                    NULL, 1, MALLOC_CAP_SPIRAM);
}

/*目的：安全、完整地从环形缓冲区读取指定长度的数据。
作用：环形缓冲区可能将数据分成多个不连续的内存块存储。
这个函数通过循环，确保即使数据被分段，也能完整地读取 size 字节到一块连续的线性内存 to_buffer 中，
以供编码器使用。这是使用环形缓冲区时的标准做法。
*/
void copy_from_ringbuf(RingbufHandle_t ringBuf, uint8_t *to_buff, int size)
{
    while (size > 0)
    {
        size_t real_len = 0;
        // 1. 从环形缓冲区请求最多 `size` 字节的数据port
        // 2.portMAX_DELAY是一直等待接收，所以没有收到的时候是一直卡着
        void *data = xRingbufferReceiveUpTo(ringBuf, &real_len, portMAX_DELAY, size);
        // 3.将获得的数据拷贝到目标缓存区
        memcpy(to_buff, data, real_len);
        vRingbufferReturnItem(ringBuf, data);   // 归还缓冲区
        // 4.更新剩余要读取的字节数和目标指针位置
        size -= real_len;
        to_buff += real_len;
    }
}

// 在一个独立的线程中无限循环，执行“读取-编码-写入”的工作流。
void audio_encoder_task(void *args)
{
    // 1. 分配内存
    int in_size = 0, out_size = 0;
    esp_opus_enc_get_frame_size(encoder, &in_size, &out_size);         // 查询编码器所需帧大小
    uint8_t *in_data = heap_caps_malloc(in_size, MALLOC_CAP_SPIRAM);   // 为原始PCM数据分配内存
    uint8_t *out_data = heap_caps_malloc(out_size, MALLOC_CAP_SPIRAM); // 为编码后数据分配内存
                                                                       // 2. 初始化帧结构体
    esp_audio_enc_in_frame_t in_frame = {
        .buffer = in_data, // 指向输入缓冲区
        .len = in_size,    // 输入缓冲区大小
    };
    esp_audio_enc_out_frame_t out_frame = {
        .buffer = out_data, // 指向输出缓冲区
        .len = out_size,    // 输出缓冲区大小
    };
    
    // 3. 主循环：持续编码
    while (my_encoder.is_running)
    {
        // a. 从输入环形缓冲区读取一帧PCM数据
        
        copy_from_ringbuf(my_encoder.input, in_frame.buffer, in_frame.len);
        // 记录编码前的PCM数据（可选，用于调试）
        // MY_LOGI("[ENCODER] PCM frame size: %d bytes", in_frame.len);
        
        // b. 核心操作：进行Opus编码
        //    输入：in_frame (装满PCM数据)
        //    输出：out_frame (被填入编码后的数据), out_frame.encoded_bytes会被设置为实际编码后的数据长度
        esp_opus_enc_process(encoder, &in_frame, &out_frame);
        // c. 将编码后的数据发送到输出环形缓冲区
        //    只发送实际编码产生的字节数(out_frame.encoded_bytes)，而不是整个缓冲区(out_size)
        xRingbufferSend(my_encoder.output, out_frame.buffer, out_frame.encoded_bytes, 0);
    }
    // 4. 清理工作：任务结束时释放内存
    free(in_data);
    free(out_data);

    // 5. 删除自身任务
    vTaskDelete(NULL);
}
